package com.rslai.base.tool.servertest.reporter;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rslai.base.tool.autotest.CaseIdHolder;
import com.rslai.base.tool.servertest.command.CallStepCommand;
import com.rslai.base.tool.servertest.command.StepCommand;
import com.rslai.base.tool.servertest.context.Context;
import com.rslai.base.tool.servertest.dsl.DSLCommand;
import com.rslai.base.tool.servertest.dsl.DSLCommandDesc;
import com.rslai.base.tool.servertest.event.StepEventListener;
import com.rslai.base.tool.servertest.model.CaseStatistics;
import com.rslai.base.tool.servertest.model.KeyValueStore;
import com.rslai.base.tool.servertest.model.ServiceDesc;
import com.rslai.base.tool.servertest.model.SvnInfo;
import com.rslai.base.tool.servertest.model.TestCase;
import com.rslai.base.tool.servertest.model.TestSuite;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class QJSONReporter implements Reporter {
    private static final Logger logger = LoggerFactory.getLogger(QJSONReporter.class);

    private JSONArray reports = new JSONArray();
    private Appendable out;
    private SvnInfo svninfo;
    private Map<Object, Object> suiteMap;
    private CaseStatistics caseStatistics = new CaseStatistics();

    public static Map<String, ServiceDesc> serviceDescMap = new HashMap();

    private List<Map<String, String>> dslCommands = new ArrayList();

    public QJSONReporter(Appendable out) {
        this.out = out;
    }

    public void done() {
        this.reports.add(this.suiteMap);
    }

    public void close() {
        try {
            this.out.append(reportAsString());
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        } finally {
            close(this.out);
        }
    }

    private void close(Appendable output) {
        try {
            if ((output instanceof Flushable)) {
                ((Flushable) output).flush();
            }
            if ((output instanceof Closeable))
                ((Closeable) output).close();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    public Map<Object, Object> getSuiteMap() {
        return this.suiteMap;
    }

    public synchronized void addFailed(String id) {
        this.caseStatistics.addFailed(id);
    }

    public synchronized void addSuccess() {
        this.caseStatistics.addSuccess();
    }

    public CaseStatistics getCaseStatistics() {
        return this.caseStatistics;
    }

    public String reportAsString() {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("svnUrl", this.svninfo.getUrl());
        jsonObject.put("reversion", this.svninfo.getReversion());
        jsonObject.put("features", this.reports);
        jsonObject.put("services", serviceDescMap.values());
        jsonObject.put("dsl", this.dslCommands);
        return jsonObject.toJSONString();
    }

    public void report(TestSuite testSuite) {
        this.suiteMap = testSuite.asMap();
    }

    private List<Object> getSuiteElements() {
        if (this.suiteMap == null) {
            return new ArrayList();
        }
        List elements = (List) this.suiteMap.get("elements");
        if (elements == null) {
            elements = new ArrayList();
            this.suiteMap.put("elements", elements);
        }
        return elements;
    }

    private Map<Object, List<Object>> getFeatureElement() {
        return (Map) getSuiteElements().get(getSuiteElements().size() - 1);
    }

    private List<Object> getSteps() {
        List steps = (List) getFeatureElement().get("steps");
        if (steps == null) {
            steps = new ArrayList();
            getFeatureElement().put("steps", steps);
        }
        return steps;
    }

    private JSONArray getRows(List<KeyValueStore> params) {
        JSONArray rowsArray = new JSONArray();
        if ((params == null) || (params.size() == 0)) return rowsArray;
        for (KeyValueStore p : params) {
            JSONObject row = new JSONObject();
            JSONArray cells = new JSONArray();
            cells.add(p.getName());

            Object value = p.getValue();
            String rVal;
            if ((value != null) && (!StringUtils.isEmpty(value.toString())))
                rVal = StringEscapeUtils.escapeXml(value.toString());
            else {
                rVal = "null";
            }
            cells.add(rVal);
            row.put("cells", cells);
            rowsArray.add(row);
        }
        return rowsArray;
    }

    public ReporterEventListener createStepListener() {
        return new ReporterEventListener();
    }

    public void addSvnInfo(SvnInfo svnInfo) {
        this.svninfo = svnInfo;
    }

    public void addService(ServiceDesc serviceDesc) {
        serviceDescMap.put(serviceDesc.getId(), serviceDesc);
    }

    public void addDSLCommand(DSLCommandDesc dslCommandDesc) {
        Map dslCommand = new HashMap();
        dslCommand.put("id", dslCommandDesc.id());
        dslCommand.put("desc", dslCommandDesc.desc());
        this.dslCommands.add(dslCommand);
    }

    public class ReporterEventListener implements StepEventListener {
        private long startTime;

        public ReporterEventListener() {
        }

        public void caseStarted(TestCase testCase, Context context) {
            Object job = context.getContext("job");
            Object build = context.getContext("build");
            if (isOnJenkins(job, build))
                CaseIdHolder.set(String.format("%s_%s_%s", new Object[]{job.toString(), build.toString(), testCase.getId()}));
            else {
                CaseIdHolder.clear();
            }
            QJSONReporter.logger.info("---TestCase {}--{} 开始运行---", testCase.getId(), testCase.getDesc());
            Map e = testCase.asMap();
            e.put("start", new Date());
            QJSONReporter.this.getSuiteElements().add(e);
        }

        private boolean isOnJenkins(Object job, Object build) {
            return (job != null) && (build != null);
        }

        public void caseFinished(TestCase testCase, Context context) {
            CaseIdHolder.clear();
            QJSONReporter.logger.info("---TestCase {}--{} 运行完毕---", testCase.getId(), testCase.getDesc());
        }

        public void stepStarted(StepCommand sc) {
            if ((sc instanceof CallStepCommand)) {
                CallStepCommand callStepCommand = (CallStepCommand) sc;
                ServiceDesc serviceDesc = (ServiceDesc) QJSONReporter.serviceDescMap.get(callStepCommand.serviceId());
                if (serviceDesc != null) {
                    serviceDesc.called();
                }
            }
            this.startTime = System.nanoTime();
        }

        public void stepFailed(StepCommand sc, Throwable e) {
            Map details = sc.toReport();
            if (details.get("dslReport") == null) {
                long duration = System.nanoTime() - this.startTime;
                if ((sc instanceof CallStepCommand)) {
                    CallStepCommand callStepCommand = (CallStepCommand) sc;
                    ServiceDesc serviceDesc = (ServiceDesc) QJSONReporter.serviceDescMap.get(callStepCommand.serviceId());
                    if (serviceDesc != null) {
                        serviceDesc.addDuration(Long.valueOf(duration));
                    }
                }

                Map result = new HashMap();
                Map stepMap = new HashMap();
                result.put("duration", Long.valueOf(duration));
                result.put("status", "failed");
                if (e != null) {
                    result.put("error_message", e.getMessage());
                }
                append(details, stepMap, duration);
                stepMap.put("result", result);
                QJSONReporter.this.getSteps().add(stepMap);
            } else {
                dslStepFailed(details, e);
            }
        }

        private void append(Map<String, Object> details, Map<Object, Object> stepMap, long duration) {
            if (details != null) {
                String stepName = (String) details.get("stepName");
                List params = (List) details.get("params");
                String name = details.get("name") == null ? "" : details.get("name").toString();

                stepMap.put("keyword", stepName + " (" + duration / 1000000L + "ms)");
                stepMap.put("name", name);
                if (!QJSONReporter.this.getRows(params).isEmpty())
                    stepMap.put("rows", QJSONReporter.this.getRows(params));
            }
        }

        public void stepFinished(StepCommand sc) {
            Map details = sc.toReport();
            if (details.get("dslReport") == null) {
                long duration = System.nanoTime() - this.startTime;
                if ((sc instanceof CallStepCommand)) {
                    CallStepCommand callStepCommand = (CallStepCommand) sc;
                    ServiceDesc serviceDesc = (ServiceDesc) QJSONReporter.serviceDescMap.get(callStepCommand.serviceId());
                    if (serviceDesc != null) {
                        serviceDesc.callSuccess();
                        serviceDesc.addDuration(Long.valueOf(duration));
                    }
                }

                Map result = new HashMap();
                Map stepMap = new HashMap();
                result.put("duration", Long.valueOf(duration));
                result.put("status", "passed");
                stepMap.put("result", result);
                append(details, stepMap, duration);
                QJSONReporter.this.getSteps().add(stepMap);
            } else {
                List dslReprotList = (List) details.get("dslReport");
                int count = dslReprotList.size();
                for (int i = 0; i < count; i++) {
                    Map current = (Map) dslReprotList.get(i);

                    Map result = new HashMap();
                    Map stepMap = new HashMap();
                    result.put("duration", current.get("duration"));
                    result.put("status", "passed");
                    stepMap.put("result", result);
                    append((Map) dslReprotList.get(i), stepMap, Long.valueOf(current.get("duration").toString()).longValue());
                    QJSONReporter.this.getSteps().add(stepMap);
                }
                DSLCommand.reportList.clear();
            }
        }

        private void dslStepFailed(Map<String, Object> details, Throwable e) {
            List dslReprotList = (List) details.get("dslReport");
            int count = dslReprotList.size();
            for (int i = 0; i < count; i++) {
                Map current = (Map) dslReprotList.get(i);
                Map result = new HashMap();
                Map stepMap = new HashMap();
                result.put("duration", current.get("duration"));
                result.put("status", "passed");
                stepMap.put("result", result);
                append(current, stepMap, Long.valueOf(current.get("duration").toString()).longValue());
                QJSONReporter.this.getSteps().add(stepMap);
            }

            StepCommand lastCommand = (StepCommand) details.get("currentCommand");
            while ((lastCommand instanceof DSLCommand)) {
                lastCommand = ((DSLCommand) lastCommand).getCurrentCommand();
            }
            long lastDuration = System.nanoTime() - this.startTime;
            if ((lastCommand instanceof CallStepCommand)) {
                CallStepCommand callStepCommand = (CallStepCommand) lastCommand;
                ServiceDesc serviceDesc = (ServiceDesc) QJSONReporter.serviceDescMap.get(callStepCommand.serviceId());
                if (serviceDesc != null) {
                    serviceDesc.addDuration(Long.valueOf(lastDuration));
                }
            }

            Map result = new HashMap();
            Map stepMap = new HashMap();
            result.put("duration", Long.valueOf(lastDuration));
            result.put("status", "failed");
            if (e != null) {
                result.put("error_message", e.getMessage());
            }
            stepMap.put("result", result);

            append(lastCommand.toReport(), stepMap, lastDuration);
            QJSONReporter.this.getSteps().add(stepMap);
            DSLCommand.reportList.clear();
        }
    }
}
